const mediaSoup = require("mediasoup");//(https://mediasoup.org/documentation/v3/mediasoup/api/)mediaSoup v3 API文档
const fs = require('fs');//(http://nodejs.cn/api/)Node.js文档
const path = require('path');//(http://nodejs.cn/api/)Node.js文档
const {createServer:createHttpsServer} = require('https');//(http://nodejs.cn/api/)Node.js文档
const express = require('express');//(https://www.expressjs.com.cn/)基于Node.js平台，快速、开放、极简的Web开发框架Express
const serveIndex = require('serve-index');//(https://www.npmjs.com/package/serve-index)serve-index提供包含给定路径的目录列表的页面
const { Server:socketIOServer } = require('socket.io');//(https://socket.io/)双向和低延迟通信socket.io
const { AwaitQueue } = require('awaitqueue');//(https://www.npmjs.com/package/awaitqueue)任务队列
const queue = new AwaitQueue();//任务队列：创建房间时避免两个房间同名
const config = require('./config');//（自定义）配置文件
const {getIPAddress} = require('./util/OtherUtils');//（自定义）工具类
const MyRoom = require('./MyRoom');//（自定义）房间类，用于管理房间中的操作

let expressApp;//expressApp应用
let httpsServer;//Web服务https
let socketIoServer;//socket.io服务

let mediaSoupWorkers = [];//存放Worker(C++子进程)
let nextWorkerIdx = 0;//下一个使用的Worker下标（轮询选择，系统多cpu时为了利用率变高，多开几个worker进程）
const roomsMap = new Map();//（房间总数）所有房间{roomId:Room}

(async () => {
    try {
        await runHttpsServer();//1.启动https服务
        await runSocketIoServer();//2.绑定socket.io到https，并配置socket.io的处理程序
        await createMediaSoupWorkers();//3.初始化可用的mediaSoup Worker数组
    } catch (err) {
        console.error(err);
    }
})();

/*1.启动https服务*/
async function runHttpsServer() {
    const {sslKey, sslCrt, listenIp, listenPort ,mediaSoupSettings:{webRtcTransportOptions:{listenIps:[{announcedIp}]}}} = config;
    //fs.existsSync('路径')如果路径存在则返回true，否则返回false
    if (!fs.existsSync(path.resolve(__dirname,sslKey)) || !fs.existsSync(path.resolve(__dirname,sslCrt))) {
        console.error('未找到 SSL 文件,检查你的 config.js 文件');
        process.exit(0);//node.js终止进程
    }
    {
        expressApp = express();
        expressApp.use(express.json());
        expressApp.use('/public',serveIndex(path.resolve(__dirname,'../public/openDir')));//访问public暴露
        expressApp.use('/',express.static(path.resolve(__dirname, '../public')));//设置静态资源地址，默认访问index.html
        expressApp.use((error, req, res, next) => {
            if (error) {
                error.status = error.status || (error.name === 'TypeError' ? 400 : 500);
                res.statusMessage = error.message;
                res.status(error.status).send(String(error));
            } else {
                next();
            }
        });
    }//创建并初始化Express App
    httpsServer = createHttpsServer({
        key : fs.readFileSync(path.resolve(__dirname,sslKey)),//fs.readFileSync('文件路径')返回 path 的内容
        cert: fs.readFileSync(path.resolve(__dirname,sslCrt))
    }, expressApp);
    httpsServer.on('error', (err) => { console.error('启动 Web 服务器失败:', err.message); });
    await new Promise((resolve) => {
        /*监听端口*/
        httpsServer.listen(listenPort, listenIp, () => {
            console.info(`服务器正在运行 https://${announcedIp}:${listenPort}`);
            resolve();
        });
    });
}
/*2.绑定socket.io到https，并配置socket.io的处理程序*/
function runSocketIoServer() {
    socketIoServer = new socketIOServer(httpsServer, {
        serveClient: false,//是否可作为客户端
        path: '/server',//连接路径
    });
    socketIoServer.use((socket, next) => {
        const {auth:{token},query:{roomId}} = socket.handshake;
        if(token!="MEETING_CONNECT"){
            next(new Error("没有权限连接"));
        }else if(!roomId){
            next(new Error("房间号必须要有"));
        }else{
            next();//通过验证
        }
    });
    /*客户端连接*/
    socketIoServer.on('connection', (socket)=> {
        const {query:{roomId,userName}} = socket.handshake;//获取参数（房间号和用户名）
        console.info(`room:${roomId}-${userName}-${socket.id} 连接成功`);
        /*任务队列避免，同一时间两个相同房间创建*/
        queue.push(async () =>{
            const myRoom = await getOrCreateRoom(roomId);//创建或者获取房间
            myRoom.initClientSocket(userName, socket);//处理socket请求
        }).catch((error) =>{ console.log(`创建房间或加入房间失败:${error}`); });
    });
}
/*3.初始化可用的mediaSoup Worker数组*/
async function createMediaSoupWorkers() {
    const {mediaSoupSettings:{numWorkers,workerSettings}}=config;
    for (let i = 0; i < numWorkers; ++i){
        const worker = await mediaSoup.createWorker(workerSettings);//使用给定的设置创建一个新的Worker
        worker.on('died', () =>{
            console.error(`当Worker意外死亡时触发died事件（这不应该发生）如果发生这种情况，那就是一个错误：2s后退出系统...`);
            setTimeout(() => process.exit(1), 2000);//Worker意外死亡，2s后退出进程
        });
        mediaSoupWorkers.push(worker);
    }
}

/*（工具类）获取下一个worker，实现多worker工作 */
function getMediaSoupWorker() {
    const worker = mediaSoupWorkers[nextWorkerIdx];
    if (++nextWorkerIdx === mediaSoupWorkers.length){ nextWorkerIdx = 0; }
    return worker;
}
/*（工具类）获取或创建房间 */
async function getOrCreateRoom(roomId) {
    let room = roomsMap.get(roomId);
    if (!room) {
        const mediaSoupWorker = getMediaSoupWorker();/*找到当前可用的worker*/
        room = await MyRoom.createRoom({mediaSoupWorker, roomId});
        room.on('close', () => {
            console.log(`房间【${roomId}】关闭了`)
            roomsMap.delete(roomId);
        });
        roomsMap.set(roomId,room);
    }
    return room;
}



